package com.ghd.dataviz.security.filter;

import com.ghd.dataviz.core.constant.ResponseCode;
import com.ghd.dataviz.core.exception.BusinessException;
import com.ghd.dataviz.core.param.DatavizResponse;
import com.ghd.dataviz.core.security.JwtTokenUtil;
import com.ghd.dataviz.core.security.impl.UserDetailsImpl;
import com.ghd.dataviz.core.utils.JsonUtil;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 自定义JWT认证过滤器
 * 该类继承自BasicAuthenticationFilter，在doFilterInternal方法中，
 * 从http头的Authorization 项读取token数据，然后用Jwts包提供的方法校验token的合法性。
 * 如果校验通过，就认为这是一个取得授权的合法请求
 */
@Slf4j
public class JWTAuthenticationFilter extends BasicAuthenticationFilter {

    private static final Logger logger = LoggerFactory.getLogger(JWTAuthenticationFilter.class);

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String header = request.getHeader(JwtTokenUtil.TOKEN_HEADER);
        if (header == null || !header.startsWith(JwtTokenUtil.TOKEN_PREFIX)) {
            chain.doFilter(request, response);
            return;
        }
        try {
            UsernamePasswordAuthenticationToken authentication = getAuthentication(header);
            SecurityContextHolder.getContext().setAuthentication(authentication);
            chain.doFilter(request, response);
        } catch (Exception e) {
            SecurityContextHolder.clearContext();
            onUnsuccessfulAuthentication(request, response, e);
            return;
        }
    }

    @SuppressWarnings("unchecked")
    private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) {
        String token = tokenHeader.replace(JwtTokenUtil.TOKEN_PREFIX, "");
        if (JwtTokenUtil.isExpiration(token)) {
            throw new BusinessException("token过期");
        }
        String json = JwtTokenUtil.getProperties(token);
        Map<String, Object> jsonObject = (Map<String, Object>) JsonUtil.jsonToPojo(json, HashMap.class);
        UserDetailsImpl userDetails = JsonUtil.jsonToPojo(json, UserDetailsImpl.class);
        if (jsonObject.containsKey("authorities")) {

            userDetails.setAuthorities(((List<Map<String, Object>>)jsonObject.get("authorities")).stream().map(a ->
                    new SimpleGrantedAuthority((String) a.get("authority")))
                    .collect(Collectors.toList()));
        }
        if (userDetails != null) {
            return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
        }
        return null;
    }

    protected void onUnsuccessfulAuthentication(HttpServletRequest request,
                                                HttpServletResponse response,
                                                Exception failed) throws IOException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");

        DatavizResponse datavizResponse;
        if (failed instanceof ExpiredJwtException) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            datavizResponse = DatavizResponse.failure(ResponseCode.EXPIRED_JWT).stack(failed).build();
        } else {
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            datavizResponse = DatavizResponse.failure(failed);
        }
        log.error("", failed);
        response.getWriter().write(datavizResponse.toString());
    }
}
